@jsenv/snapshot 2.8.4 → 2.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/snapshot",
3
- "version": "2.8.4",
3
+ "version": "2.8.5",
4
4
  "description": "Snapshot testing",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -35,9 +35,9 @@
35
35
  "dependencies": {
36
36
  "@jsenv/assert": "4.1.15",
37
37
  "@jsenv/ast": "6.2.14",
38
- "@jsenv/exception": "1.0.1",
39
- "@jsenv/filesystem": "4.9.9",
40
- "@jsenv/terminal-recorder": "1.4.3",
38
+ "@jsenv/exception": "1.0.2",
39
+ "@jsenv/filesystem": "4.9.10",
40
+ "@jsenv/terminal-recorder": "1.4.4",
41
41
  "@jsenv/urls": "2.5.2",
42
42
  "@jsenv/utils": "2.1.2",
43
43
  "ansi-regex": "6.0.1",
@@ -180,7 +180,7 @@ export const takeDirectorySnapshot = (
180
180
  return URL_META.urlChildMayMatch({
181
181
  url,
182
182
  associations,
183
- predicate: (meta) => Boolean(meta.action),
183
+ predicate: (meta) => meta.action && meta.action !== "ignore",
184
184
  });
185
185
  };
186
186
  const shouldIncludeFile = (url) => {
@@ -188,9 +188,13 @@ export const takeDirectorySnapshot = (
188
188
  url,
189
189
  associations,
190
190
  });
191
- return meta.action === true || meta.action === "presence_only";
191
+ return (
192
+ meta.action === true ||
193
+ meta.action === "compare" ||
194
+ meta.action === "compare_presence_only"
195
+ );
192
196
  };
193
- const shouldCompareFile = (url) => {
197
+ const shouldCompareFileContent = (url) => {
194
198
  const meta = URL_META.applyAssociations({
195
199
  url,
196
200
  associations,
@@ -200,7 +204,7 @@ export const takeDirectorySnapshot = (
200
204
  const directorySnapshot = createDirectorySnapshot(directoryUrl, {
201
205
  shouldVisitDirectory,
202
206
  shouldIncludeFile,
203
- shouldCompareFile,
207
+ shouldCompareFileContent,
204
208
  clean: true,
205
209
  });
206
210
  return {
@@ -209,7 +213,7 @@ export const takeDirectorySnapshot = (
209
213
  const nextDirectorySnapshot = createDirectorySnapshot(directoryUrl, {
210
214
  shouldVisitDirectory,
211
215
  shouldIncludeFile,
212
- shouldCompareFile,
216
+ shouldCompareFileContent,
213
217
  });
214
218
  directorySnapshot.compare(nextDirectorySnapshot, { throwWhenDiff });
215
219
  },
@@ -237,7 +241,7 @@ export const takeDirectorySnapshot = (
237
241
  };
238
242
  const createDirectorySnapshot = (
239
243
  directoryUrl,
240
- { shouldVisitDirectory, shouldIncludeFile, shouldCompareFile, clean },
244
+ { shouldVisitDirectory, shouldIncludeFile, shouldCompareFileContent, clean },
241
245
  ) => {
242
246
  const directorySnapshot = {
243
247
  type: "directory",
@@ -322,7 +326,7 @@ ${extraUrls.join("\n")}`);
322
326
  // content
323
327
  {
324
328
  for (const relativeUrl of relativeUrls) {
325
- if (!shouldCompareFile(new URL(relativeUrl, directoryUrl))) {
329
+ if (!shouldCompareFileContent(new URL(relativeUrl, directoryUrl))) {
326
330
  continue;
327
331
  }
328
332
  const snapshot = directoryContentSnapshot[relativeUrl];
@@ -384,7 +388,7 @@ ${extraUrls.join("\n")}`);
384
388
  {
385
389
  shouldVisitDirectory,
386
390
  shouldIncludeFile,
387
- shouldCompareFile,
391
+ shouldCompareFileContent,
388
392
  clean,
389
393
  },
390
394
  );
@@ -4,13 +4,15 @@ import { filesystemSideEffects } from "./filesystem/filesystem_side_effects.js";
4
4
  import { logSideEffects } from "./log/log_side_effects.js";
5
5
 
6
6
  export const createCaptureSideEffects = ({
7
+ sourceFileUrl,
7
8
  logEffects = true,
8
9
  filesystemEffects = true,
10
+ filesystemActions,
9
11
  rootDirectoryUrl,
10
12
  replaceFilesystemWellKnownValues = createReplaceFilesystemWellKnownValues({
11
13
  rootDirectoryUrl,
12
14
  }),
13
- } = {}) => {
15
+ }) => {
14
16
  const detectors = [];
15
17
  if (logEffects) {
16
18
  detectors.push(logSideEffects(logEffects === true ? {} : logEffects));
@@ -20,7 +22,9 @@ export const createCaptureSideEffects = ({
20
22
  filesystemSideEffectsDetector = filesystemSideEffects(
21
23
  filesystemEffects === true ? {} : filesystemEffects,
22
24
  {
25
+ sourceFileUrl,
23
26
  replaceFilesystemWellKnownValues,
27
+ filesystemActions,
24
28
  },
25
29
  );
26
30
  detectors.push(filesystemSideEffectsDetector);
@@ -7,17 +7,16 @@ import { groupFileSideEffectsPerDirectory } from "./group_file_side_effects_per_
7
7
  import { spyFilesystemCalls } from "./spy_filesystem_calls.js";
8
8
 
9
9
  const filesystemSideEffectsOptionsDefault = {
10
- include: null,
11
10
  preserve: false,
12
11
  baseDirectory: "",
13
- textualFilesIntoDirectory: false,
12
+ textualFilesInline: false,
14
13
  };
15
14
  const INLINE_MAX_LINES = 20;
16
15
  const INLINE_MAX_LENGTH = 2000;
17
16
 
18
17
  export const filesystemSideEffects = (
19
18
  filesystemSideEffectsOptions,
20
- { replaceFilesystemWellKnownValues },
19
+ { sourceFileUrl, filesystemActions, replaceFilesystemWellKnownValues },
21
20
  ) => {
22
21
  filesystemSideEffectsOptions = {
23
22
  ...filesystemSideEffectsOptionsDefault,
@@ -42,10 +41,11 @@ export const filesystemSideEffects = (
42
41
  name: "filesystem",
43
42
  setBaseDirectory,
44
43
  install: (addSideEffect, { addSkippableHandler, addFinallyCallback }) => {
45
- let { include, preserve, textualFilesIntoDirectory } =
46
- filesystemSideEffectsOptions;
44
+ let { preserve, textualFilesInline } = filesystemSideEffectsOptions;
47
45
  if (filesystemSideEffectsOptions.baseDirectory) {
48
46
  setBaseDirectory(filesystemSideEffectsOptions.baseDirectory);
47
+ } else if (sourceFileUrl) {
48
+ setBaseDirectory(new URL("./", sourceFileUrl));
49
49
  }
50
50
  const getUrlRelativeToBase = (url) => {
51
51
  if (baseDirectory) {
@@ -155,7 +155,7 @@ export const filesystemSideEffects = (
155
155
  if (outDirectoryReason) {
156
156
  const outUrlRelativeToCommonDirectory = urlToRelativeUrl(
157
157
  text.urlInsideOutDirectory,
158
- options.sideEffectFileUrl,
158
+ options.sideEffectMdFileUrl,
159
159
  );
160
160
  groupMd += `${"#".repeat(2)} ${urlRelativeToCommonDirectory}
161
161
  ${renderFileContent(
@@ -193,7 +193,7 @@ ${renderFileContent(
193
193
  );
194
194
  const commonDirectoryOutRelativeUrl = urlToRelativeUrl(
195
195
  commonDirectoryOutUrl,
196
- options.sideEffectFileUrl,
196
+ options.sideEffectMdFileUrl,
197
197
  { preferRelativeNotation: true },
198
198
  );
199
199
  return {
@@ -219,12 +219,14 @@ ${renderFileContent(
219
219
  const isTextual = CONTENT_TYPE.isTextual(contentType);
220
220
  let outDirectoryReason;
221
221
  if (isTextual) {
222
- if (textualFilesIntoDirectory) {
223
- outDirectoryReason = "textual_in_directory_option";
224
- } else if (String(buffer).split("\n").length > INLINE_MAX_LINES) {
225
- outDirectoryReason = "lot_of_lines";
226
- } else if (buffer.size > INLINE_MAX_LENGTH) {
227
- outDirectoryReason = "lot_of_chars";
222
+ if (textualFilesInline) {
223
+ if (String(buffer).split("\n").length > INLINE_MAX_LINES) {
224
+ outDirectoryReason = "lot_of_lines";
225
+ } else if (buffer.size > INLINE_MAX_LENGTH) {
226
+ outDirectoryReason = "lot_of_chars";
227
+ }
228
+ } else {
229
+ outDirectoryReason = "text";
228
230
  }
229
231
  } else {
230
232
  outDirectoryReason = "binary";
@@ -240,7 +242,7 @@ ${renderFileContent(
240
242
  outDirectoryReason,
241
243
  },
242
244
  render: {
243
- md: ({ sideEffectFileUrl, generateOutFileUrl }) => {
245
+ md: ({ sideEffectMdFileUrl, generateOutFileUrl }) => {
244
246
  const urlRelativeToBase = getUrlRelativeToBase(url);
245
247
  if (outDirectoryReason) {
246
248
  let urlInsideOutDirectory = getUrlInsideOutDirectory(
@@ -267,7 +269,7 @@ ${renderFileContent(
267
269
  }
268
270
  const outRelativeUrl = urlToRelativeUrl(
269
271
  urlInsideOutDirectory,
270
- sideEffectFileUrl,
272
+ sideEffectMdFileUrl,
271
273
  {
272
274
  preferRelativeNotation: true,
273
275
  },
@@ -311,7 +313,7 @@ ${renderFileContent(
311
313
  },
312
314
  },
313
315
  {
314
- include,
316
+ include: filesystemActions,
315
317
  undoFilesystemSideEffects: !preserve,
316
318
  },
317
319
  );
@@ -28,9 +28,20 @@ export const spyFilesystemCalls = (
28
28
  },
29
29
  { include, undoFilesystemSideEffects } = {},
30
30
  ) => {
31
- const shouldReport = include
32
- ? URL_META.createFilter(include, "file:///")
33
- : () => true;
31
+ const getAction = include
32
+ ? (() => {
33
+ const associations = URL_META.resolveAssociations(
34
+ {
35
+ action: include,
36
+ },
37
+ "file:///",
38
+ );
39
+ return (url) => {
40
+ const meta = URL_META.applyAssociations({ url, associations });
41
+ return meta.action;
42
+ };
43
+ })()
44
+ : () => "compare";
34
45
 
35
46
  const _internalFs = process.binding("fs");
36
47
  const filesystemStateInfoMap = new Map();
@@ -58,35 +69,49 @@ export const spyFilesystemCalls = (
58
69
  // function did not have any effect on the file
59
70
  return;
60
71
  }
61
- if (!shouldReport(fileUrl)) {
62
- return;
63
- }
64
- if (undoFilesystemSideEffects && !fileRestoreMap.has(fileUrl)) {
65
- if (stateBefore.found) {
66
- fileRestoreMap.set(fileUrl, () => {
67
- writeFileSync(fileUrl, stateBefore.buffer);
68
- });
69
- } else {
70
- fileRestoreMap.set(fileUrl, () => {
71
- removeFileSync(fileUrl, { allowUseless: true });
72
- });
72
+ const action = getAction(fileUrl);
73
+ const shouldCompare =
74
+ action === "compare" ||
75
+ action === "compare_presence_only" ||
76
+ action === true;
77
+ if (action === "undo" || shouldCompare) {
78
+ if (undoFilesystemSideEffects && !fileRestoreMap.has(fileUrl)) {
79
+ if (stateBefore.found) {
80
+ fileRestoreMap.set(fileUrl, () => {
81
+ writeFileSync(fileUrl, stateBefore.buffer);
82
+ });
83
+ } else {
84
+ fileRestoreMap.set(fileUrl, () => {
85
+ removeFileSync(fileUrl, { allowUseless: true });
86
+ });
87
+ }
73
88
  }
74
89
  }
75
- onWriteFile(fileUrl, stateAfter.buffer, reason);
90
+ if (shouldCompare) {
91
+ onWriteFile(fileUrl, stateAfter.buffer, reason);
92
+ }
93
+ // "ignore", false, anything else
76
94
  };
77
95
  const onWriteDirectoryDone = (directoryUrl) => {
78
- if (!shouldReport(directoryUrl)) {
79
- return;
80
- }
81
- if (undoFilesystemSideEffects && !fileRestoreMap.has(directoryUrl)) {
82
- fileRestoreMap.set(directoryUrl, () => {
83
- removeDirectorySync(directoryUrl, {
84
- allowUseless: true,
85
- recursive: true,
96
+ const action = getAction(directoryUrl);
97
+ const shouldCompare =
98
+ action === "compare" ||
99
+ action === "compare_presence_only" ||
100
+ action === true;
101
+ if (action === "undo" || shouldCompare) {
102
+ if (undoFilesystemSideEffects && !fileRestoreMap.has(directoryUrl)) {
103
+ fileRestoreMap.set(directoryUrl, () => {
104
+ removeDirectorySync(directoryUrl, {
105
+ allowUseless: true,
106
+ recursive: true,
107
+ });
86
108
  });
87
- });
109
+ }
110
+ }
111
+ if (shouldCompare) {
112
+ onWriteDirectory(directoryUrl);
88
113
  }
89
- onWriteDirectory(directoryUrl);
114
+ // "ignore", false, anything else
90
115
  };
91
116
  const restoreCallbackSet = new Set();
92
117
 
@@ -37,8 +37,7 @@ export const createBigSizeEffect =
37
37
  export const renderSideEffects = (
38
38
  sideEffects,
39
39
  {
40
- sideEffectFileUrl,
41
- outDirectoryUrl,
40
+ sideEffectMdFileUrl,
42
41
  generateOutFileUrl,
43
42
  generatedBy = true,
44
43
  titleLevel = 1,
@@ -86,8 +85,7 @@ export const renderSideEffects = (
86
85
  markdown += "\n\n";
87
86
  }
88
87
  markdown += renderOneSideEffect(sideEffect, {
89
- sideEffectFileUrl,
90
- outDirectoryUrl,
88
+ sideEffectMdFileUrl,
91
89
  generateOutFileUrl,
92
90
  rootDirectoryUrl,
93
91
  titleLevel,
@@ -135,8 +133,7 @@ ${" ".repeat(indent)}</sub>`;
135
133
  const renderOneSideEffect = (
136
134
  sideEffect,
137
135
  {
138
- sideEffectFileUrl,
139
- outDirectoryUrl,
136
+ sideEffectMdFileUrl,
140
137
  generateOutFileUrl,
141
138
  rootDirectoryUrl,
142
139
  titleLevel,
@@ -155,8 +152,7 @@ const renderOneSideEffect = (
155
152
  }
156
153
  const { md } = sideEffect.render;
157
154
  let { label, text } = md({
158
- sideEffectFileUrl,
159
- outDirectoryUrl,
155
+ sideEffectMdFileUrl,
160
156
  generateOutFileUrl,
161
157
  replace,
162
158
  rootDirectoryUrl,
@@ -175,7 +171,7 @@ const renderOneSideEffect = (
175
171
  }
176
172
  text = renderText(text, {
177
173
  sideEffect,
178
- sideEffectFileUrl,
174
+ sideEffectMdFileUrl,
179
175
  generateOutFileUrl,
180
176
  replace,
181
177
  rootDirectoryUrl,
@@ -212,7 +208,7 @@ const renderText = (
212
208
  text,
213
209
  {
214
210
  sideEffect,
215
- sideEffectFileUrl,
211
+ sideEffectMdFileUrl,
216
212
  generateOutFileUrl,
217
213
  replace,
218
214
  rootDirectoryUrl,
@@ -229,7 +225,7 @@ const renderText = (
229
225
  }
230
226
  const callSiteRelativeUrl = urlToRelativeUrl(
231
227
  callSite.url,
232
- sideEffectFileUrl,
228
+ sideEffectMdFileUrl,
233
229
  { preferRelativeNotation: true },
234
230
  );
235
231
  const sourceCodeLinkText = `${callSiteRelativeUrl}:${callSite.line}:${callSite.column}`;
@@ -265,7 +261,7 @@ const renderText = (
265
261
  return renderPotentialAnsi(exceptionText, {
266
262
  stringType: "error",
267
263
  sideEffect,
268
- sideEffectFileUrl,
264
+ sideEffectMdFileUrl,
269
265
  generateOutFileUrl,
270
266
  replace,
271
267
  });
@@ -275,7 +271,7 @@ const renderText = (
275
271
  if (text.type === "console") {
276
272
  return renderConsole(text.value, {
277
273
  sideEffect,
278
- sideEffectFileUrl,
274
+ sideEffectMdFileUrl,
279
275
  generateOutFileUrl,
280
276
  replace,
281
277
  });
@@ -295,12 +291,12 @@ const renderText = (
295
291
 
296
292
  export const renderConsole = (
297
293
  string,
298
- { sideEffect, sideEffectFileUrl, generateOutFileUrl, replace },
294
+ { sideEffect, sideEffectMdFileUrl, generateOutFileUrl, replace },
299
295
  ) => {
300
296
  return renderPotentialAnsi(string, {
301
297
  stringType: "console",
302
298
  sideEffect,
303
- sideEffectFileUrl,
299
+ sideEffectMdFileUrl,
304
300
  generateOutFileUrl,
305
301
  replace,
306
302
  });
@@ -308,7 +304,7 @@ export const renderConsole = (
308
304
 
309
305
  const renderPotentialAnsi = (
310
306
  string,
311
- { stringType, sideEffect, sideEffectFileUrl, generateOutFileUrl, replace },
307
+ { stringType, sideEffect, sideEffectMdFileUrl, generateOutFileUrl, replace },
312
308
  ) => {
313
309
  const rawTextBlock = renderMarkdownBlock(
314
310
  replace(string, { stringType }),
@@ -332,7 +328,7 @@ const renderPotentialAnsi = (
332
328
  );
333
329
  svgFileContent = replace(svgFileContent, { fileUrl: svgFileUrl });
334
330
  writeFileSync(svgFileUrl, svgFileContent);
335
- const svgFileRelativeUrl = urlToRelativeUrl(svgFileUrl, sideEffectFileUrl);
331
+ const svgFileRelativeUrl = urlToRelativeUrl(svgFileUrl, sideEffectMdFileUrl);
336
332
  let md = `![img](${svgFileRelativeUrl})`;
337
333
  md += "\n\n";
338
334
  md += renderMarkdownDetails(`${rawTextBlock}`, {
@@ -1,69 +1,71 @@
1
- import { urlToBasename } from "@jsenv/urls";
2
- import {
3
- takeDirectorySnapshot,
4
- takeFileSnapshot,
5
- } from "../filesystem_snapshot.js";
1
+ import { writeFileSync } from "@jsenv/filesystem";
2
+ import { urlToBasename, urlToFilename } from "@jsenv/urls";
3
+ import { takeDirectorySnapshot } from "../filesystem_snapshot.js";
6
4
  import { createCaptureSideEffects } from "./create_capture_side_effects.js";
7
5
  import { renderSideEffects } from "./render_side_effects.js";
8
-
6
+ /**
7
+ * Generate a markdown file describing code side effects. When executed in CI throw if there is a diff.
8
+ * @param {URL} sourceFileUrl
9
+ * Url where the function is located (import.meta.url)
10
+ * @param {Function} fn
11
+ * Function to snapshot
12
+ * @param {Object} snapshotSideEffectsOptions
13
+ * @param {string|url} snapshotSideEffectsOptions.outFilePattern
14
+ * @param {string|url} snapshotSideEffectsOptions.sideEffectMdFileUrl
15
+ * Where to write the markdown file. Defaults to ./[]
16
+ * @param {string|url} snapshotSideEffectsOptions.rootDirectoryUrl
17
+ * @param {Object|boolean} [snapshotSideEffectsOptions.filesystemEffects]
18
+ * @param {boolean} [snapshotSideEffectsOptions.filesystemEffects.textualFilesInline=false]
19
+ * Put textual files content in the markdown (instead of separate files).
20
+ * Big files will still be put in dedicated files.
21
+ * @param {boolean} [snapshotSideEffectsOptions.filesystemEffects.preserve=false]
22
+ * Preserve filesystem side effect when function ends. By default
23
+ * filesystem effects are undone when function ends
24
+ * @param {url} [snapshotSideEffectsOptions.filesystemEffects.baseDirectory]
25
+ * Urls of filesystem side effects will be relative to this base directory
26
+ * Default to the directory containing @sourceFileUrl
27
+ * @return {Array.<Object>} sideEffects
28
+ */
9
29
  export const snapshotSideEffects = (
10
30
  sourceFileUrl,
11
31
  fn,
12
32
  {
13
- sideEffectFileUrl,
14
- outDirectoryPattern = "./side_effects/",
15
- sideEffectFilePattern = "./side_effects/[basename].md",
16
- outFilePattern = "./side_effects/[name]/[filename]",
17
- generateOutFileUrl,
18
- outDirectoryUrl,
33
+ sideEffectMdFileUrl,
34
+ outFilePattern = "_[source_filename]/[filename]",
19
35
  errorStackHidden,
36
+ throwWhenDiff,
20
37
  ...captureOptions
21
38
  } = {},
22
39
  ) => {
23
- const name = urlToBasename(sourceFileUrl, true);
24
- const basename = urlToBasename(sourceFileUrl);
25
- if (sideEffectFileUrl === undefined) {
26
- const sideEffectFileRelativeUrl = sideEffectFilePattern
27
- .replaceAll("[basename]", basename)
28
- .replaceAll("[name]", name);
29
- sideEffectFileUrl = new URL(sideEffectFileRelativeUrl, sourceFileUrl);
30
- } else {
31
- sideEffectFileUrl = new URL(sideEffectFileUrl, sourceFileUrl);
32
- }
33
-
34
- const captureSideEffects = createCaptureSideEffects(captureOptions);
35
- if (outDirectoryUrl === undefined) {
36
- const outDirectoryRelativeUrl = outDirectoryPattern.replaceAll(
37
- "[basename]",
38
- basename,
39
- );
40
- outDirectoryUrl = new URL(outDirectoryRelativeUrl, sideEffectFileUrl);
41
- }
42
- if (generateOutFileUrl === undefined) {
43
- generateOutFileUrl = (filename) => {
44
- const outRelativeUrl = outFilePattern
45
- .replaceAll("[name]", name)
46
- .replaceAll("[basename]", basename)
47
- .replaceAll("[filename]", filename);
48
- const outFileUrl = new URL(outRelativeUrl, new URL("./", sourceFileUrl))
49
- .href;
50
- return outFileUrl;
51
- };
52
- }
53
-
54
- const sideEffectFileSnapshot = takeFileSnapshot(sideEffectFileUrl);
40
+ const sourceName = urlToBasename(sourceFileUrl, true);
41
+ const sourceBasename = urlToBasename(sourceFileUrl);
42
+ const sourceFilename = urlToFilename(sourceFileUrl);
43
+ const generateOutFileUrl = (filename) => {
44
+ const outRelativeUrl = outFilePattern
45
+ .replaceAll("[source_name]", sourceName)
46
+ .replaceAll("[source_basename]", sourceBasename)
47
+ .replaceAll("[source_filename]", sourceFilename)
48
+ .replaceAll("[filename]", filename);
49
+ const outFileUrl = new URL(outRelativeUrl, new URL("./", sourceFileUrl))
50
+ .href;
51
+ return outFileUrl;
52
+ };
53
+ const outDirectoryUrl = generateOutFileUrl("");
54
+ sideEffectMdFileUrl =
55
+ sideEffectMdFileUrl || generateOutFileUrl(`${sourceFilename}.md`);
56
+ const captureSideEffects = createCaptureSideEffects({
57
+ ...captureOptions,
58
+ sourceFileUrl,
59
+ });
55
60
  const outDirectorySnapshot = takeDirectorySnapshot(outDirectoryUrl);
56
61
  const onSideEffects = (sideEffects) => {
57
62
  const sideEffectFileContent = renderSideEffects(sideEffects, {
58
- sideEffectFileUrl,
59
- outDirectoryUrl,
63
+ sideEffectMdFileUrl,
60
64
  generateOutFileUrl,
61
65
  errorStackHidden,
62
66
  });
63
- sideEffectFileSnapshot.update(sideEffectFileContent, {
64
- mockFluctuatingValues: false,
65
- });
66
- outDirectorySnapshot.compare();
67
+ writeFileSync(sideEffectMdFileUrl, sideEffectFileContent);
68
+ outDirectorySnapshot.compare(throwWhenDiff);
67
69
  };
68
70
  const returnValue = captureSideEffects(fn);
69
71
  if (returnValue && typeof returnValue.then === "function") {
@@ -1,30 +1,37 @@
1
- import { urlToBasename, urlToRelativeUrl } from "@jsenv/urls";
2
- import {
3
- takeDirectorySnapshot,
4
- takeFileSnapshot,
5
- } from "../filesystem_snapshot.js";
1
+ import { writeFileSync } from "@jsenv/filesystem";
2
+ import { urlToBasename, urlToFilename, urlToRelativeUrl } from "@jsenv/urls";
3
+ import { takeDirectorySnapshot } from "../filesystem_snapshot.js";
6
4
  import { getCallerLocation } from "../get_caller_location.js";
7
5
  import { createCaptureSideEffects } from "./create_capture_side_effects.js";
8
6
  import { renderSideEffects, renderSmallLink } from "./render_side_effects.js";
9
7
 
10
8
  /**
11
- * Generate a markdown file describing all test side effects. When executed in CI throw if there is a diff.
12
- * @param {URL} testFileUrl
9
+ * Generate a markdown file describing test(s) side effects. When executed in CI throw if there is a diff.
10
+ * @param {URL} sourceFileUrl
13
11
  * @param {Function} fnRegisteringTest
14
12
  * @param {Object} snapshotTestsOptions
15
- * @param {string|url} snapshotTestsOptions.sideEffectFileUrl
13
+ * @param {string|url} snapshotTestsOptions.outFilePattern
16
14
  * @param {string|url} snapshotTestsOptions.rootDirectoryUrl
17
- * @return {Array.<Object>} sideEffects
15
+ * @param {Object|boolean} [snapshotTestsOptions.filesystemEffects]
16
+ * @param {boolean} [snapshotTestsOptions.filesystemEffects.textualFilesInline=false]
17
+ * Put textual files content in the markdown (instead of separate files).
18
+ * Big files will still be put in dedicated files.
19
+ * @param {boolean} [snapshotTestsOptions.filesystemEffects.preserve=false]
20
+ * Preserve filesystem side effect when function ends. By default
21
+ * filesystem effects are undone when function ends
22
+ * @param {url} [snapshotTestsOptions.filesystemEffects.baseDirectory]
23
+ * Urls of filesystem side effects will be relative to this base directory
24
+ * Default to the directory containing @sourceFileUrl
18
25
  */
19
26
  export const snapshotTests = async (
20
- testFileUrl,
27
+ sourceFileUrl,
21
28
  fnRegisteringTest,
22
29
  {
23
- testName = urlToBasename(testFileUrl, true),
24
- sideEffectFileUrl,
25
- outDirectoryPattern = "./side_effects/",
26
- sideEffectFilePattern = "./side_effects/[test_basename].md",
27
- outFilePattern = "./side_effects/[test_name]/[test_scenario]/[filename]",
30
+ outFilePattern = "./_[source_filename]/[filename]",
31
+ filesystemActions = {
32
+ "**": "compare",
33
+ // "**/*.svg": "compare_presence_only",
34
+ },
28
35
  rootDirectoryUrl,
29
36
  generatedBy = true,
30
37
  linkToSource = true,
@@ -36,15 +43,24 @@ export const snapshotTests = async (
36
43
  throwWhenDiff = process.env.CI,
37
44
  } = {},
38
45
  ) => {
39
- const testBasename = urlToBasename(testFileUrl);
40
- if (sideEffectFileUrl === undefined) {
41
- const sideEffectFileRelativeUrl = sideEffectFilePattern
42
- .replaceAll("[test_name]", testName)
43
- .replaceAll("[test_basename]", testBasename);
44
- sideEffectFileUrl = new URL(sideEffectFileRelativeUrl, testFileUrl);
45
- } else {
46
- sideEffectFileUrl = new URL(sideEffectFileUrl, testFileUrl);
47
- }
46
+ const sourceName = urlToBasename(sourceFileUrl, true);
47
+ const sourceBasename = urlToBasename(sourceFileUrl);
48
+ const sourceFilename = urlToFilename(sourceFileUrl);
49
+ const generateOutFileUrl = (outFilename) => {
50
+ const outFileRelativeUrl = outFilePattern
51
+ .replaceAll("[source_name]", sourceName)
52
+ .replaceAll("[source_basename]", sourceBasename)
53
+ .replaceAll("[source_filename]", sourceFilename)
54
+ .replaceAll("[filename]", outFilename);
55
+ const outFileUrl = new URL(outFileRelativeUrl, sourceFileUrl).href;
56
+ return outFileUrl;
57
+ };
58
+ const outDirectoryUrl = generateOutFileUrl("");
59
+ const outDirectorySnapshot = takeDirectorySnapshot(
60
+ outDirectoryUrl,
61
+ filesystemActions,
62
+ );
63
+ const sideEffectMdFileUrl = generateOutFileUrl(`${sourceFilename}.md`);
48
64
 
49
65
  const dirUrlMap = new Map();
50
66
  const sideEffectsMap = new Map();
@@ -66,12 +82,14 @@ export const snapshotTests = async (
66
82
 
67
83
  const activeTestMap = onlyTestMap.size ? onlyTestMap : testMap;
68
84
  const captureSideEffects = createCaptureSideEffects({
85
+ sourceFileUrl,
69
86
  rootDirectoryUrl,
70
87
  logEffects,
71
88
  filesystemEffects,
89
+ filesystemActions,
72
90
  });
73
91
  let markdown = "";
74
- markdown += `# ${testName}`;
92
+ markdown += `# ${sourceName}`;
75
93
  if (generatedBy) {
76
94
  let generatedByLink = renderSmallLink(
77
95
  {
@@ -81,8 +99,8 @@ export const snapshotTests = async (
81
99
  {
82
100
  prefix: "Generated by ",
83
101
  suffix:
84
- linkToSource && testFileUrl
85
- ? generateExecutingLink(testFileUrl, sideEffectFileUrl)
102
+ linkToSource && sourceFileUrl
103
+ ? generateExecutingLink(sourceFileUrl, sideEffectMdFileUrl)
86
104
  : "",
87
105
  },
88
106
  );
@@ -90,15 +108,7 @@ export const snapshotTests = async (
90
108
  markdown += generatedByLink;
91
109
  }
92
110
 
93
- const outDirectoryRelativeUrl = outDirectoryPattern.replaceAll(
94
- "[test_name]",
95
- testName,
96
- );
97
- const outDirectoryUrl = new URL(outDirectoryRelativeUrl, testFileUrl);
98
- const outDirectorySnapshot = takeDirectorySnapshot(outDirectoryUrl, {
99
- "**/*": true,
100
- "**/*.svg": "presence_only",
101
- });
111
+ const scenarioDirs = [];
102
112
  for (const [scenario, { fn, callSite }] of activeTestMap) {
103
113
  markdown += "\n\n";
104
114
  markdown += `## ${scenario}`;
@@ -109,21 +119,15 @@ export const snapshotTests = async (
109
119
  });
110
120
  sideEffectsMap.set(scenario, sideEffects);
111
121
  const testScenario = asValidFilename(scenario);
112
- const generateOutFileUrl = (filename) => {
113
- const outFileRelativeUrl = outFilePattern
114
- .replaceAll("[test_name]", testName)
115
- .replaceAll("[test_basename]", testBasename)
116
- .replaceAll("[test_scenario]", testScenario)
117
- .replaceAll("[filename]", filename);
118
- const outFileUrl = new URL(outFileRelativeUrl, testFileUrl).href;
119
- return outFileUrl;
122
+ scenarioDirs.push(testScenario);
123
+ const generateScenarioOutFileUrl = (outfilename) => {
124
+ return generateOutFileUrl(`${testScenario}/${outfilename}`);
120
125
  };
121
- const outFileDirectoryUrl = generateOutFileUrl("");
122
- dirUrlMap.set(scenario, outFileDirectoryUrl);
126
+ const scenarioOutDirectoryUrl = generateScenarioOutFileUrl("");
127
+ dirUrlMap.set(scenario, scenarioOutDirectoryUrl);
123
128
  const sideEffectsMarkdown = renderSideEffects(sideEffects, {
124
- sideEffectFileUrl,
125
- outDirectoryUrl,
126
- generateOutFileUrl,
129
+ sideEffectMdFileUrl,
130
+ generateOutFileUrl: generateScenarioOutFileUrl,
127
131
  generatedBy: false,
128
132
  titleLevel: 3,
129
133
  errorStackHidden,
@@ -131,11 +135,23 @@ export const snapshotTests = async (
131
135
  });
132
136
  markdown += sideEffectsMarkdown;
133
137
  }
134
- const sideEffectFileSnapshot = takeFileSnapshot(sideEffectFileUrl);
135
- sideEffectFileSnapshot.update(markdown, {
136
- mockFluctuatingValues: false,
137
- throwWhenDiff,
138
- });
138
+ // if (sideEffectFilePattern === "./side_effects/[filename]/[filename].md") {
139
+ // const scenarioParentDirUrl = new URL("./", sideEffectFileUrl);
140
+ // const dirContent = readDirectorySync(scenarioParentDirUrl);
141
+ // for (const entry of dirContent) {
142
+ // const entryUrl = new URL(entry, scenarioParentDirUrl);
143
+ // if (!readEntryStatSync(entryUrl).isDirectory()) {
144
+ // continue;
145
+ // }
146
+ // if (scenarioDirs.includes(entry)) {
147
+ // continue;
148
+ // }
149
+ // removeDirectorySync(entryUrl, {
150
+ // recursive: true,
151
+ // });
152
+ // }
153
+ // }
154
+ writeFileSync(sideEffectMdFileUrl, markdown);
139
155
  outDirectorySnapshot.compare(throwWhenDiff);
140
156
 
141
157
  return { dirUrlMap, sideEffectsMap };